/*
 * FileName: DirectMemorySize.java
 * Author:   zzw
 * Date:     2018年11月08日
 * Description:
 */
package com.zzw.hotspot.sa;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.runtime.*;
import sun.jvm.hotspot.tools.*;
import sun.jvm.hotspot.utilities.*;

/**
 * 〈1.6 获得堆外内存使用〉<br>
 * 〈〉
 *
 * @author zzw
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本]（可选）
 */
public class DirectMemorySize extends Tool {
    private boolean exactMallocMode;
    private boolean verbose;

    public DirectMemorySize(boolean exactMallocMode, boolean verbose) {
        this.exactMallocMode = exactMallocMode;
        this.verbose = verbose;
    }

    @Override
    public void run() {
        // Ready to go with the database...
        try {
            long reservedMemory = getStaticLongFieldValue("java.nio.Bits", "reservedMemory");
            long directMemory = getStaticLongFieldValue("sun.misc.VM", "directMemory");

            System.out.println("NIO direct memory: (in bytes)");
            System.out.printf("reserved size = %f MB (%d bytes)\n", toM(reservedMemory), reservedMemory);
            System.out.printf(" ax size = %f MB (%d bytes)\n", toM(directMemory), directMemory);

            if (verbose) {
                System.out.println("Currently allocated direct buffers:");
            }

            if (exactMallocMode || verbose) {
                final long pageSize = getStaticIntFieldValue("java.nio.Bits", "pageSize");
                ObjectHeap heap = VM.getVM().getObjectHeap();
                InstanceKlass deallocatorKlass =
                        SystemDictionaryHelper.findInstanceKlass("java.nio.DirectByteBuffer$Deallocator");
                final LongField addressField = (LongField) deallocatorKlass.findField("address", "J");
                final IntField capacityField = (IntField) deallocatorKlass.findField("capacity", "I");
                final int[] countHolder = new int[1];

                heap.iterateObjectsOfKlass(new DefaultHeapVisitor() {
                    @Override
                    public boolean doObj(Oop oop) {
                        long address = addressField.getValue(oop);
                        if (address == 0) return false; // this deallocator has already been run

                        long capacity = capacityField.getValue(oop);
                        long mallocSize = capacity + pageSize;
                        countHolder[0]++;

                        if (verbose) {
                            System.out.printf("0x%016x: capacity = %f MB (%d bytes),"
                                            + " mallocSize = %f MB (%d bytes)\n",
                                    address, toM(capacity), capacity,
                                    toM(mallocSize), mallocSize);
                        }

                        return false;
                    }
                }, deallocatorKlass, false);

                if (exactMallocMode) {
                    long totalMallocSize = reservedMemory + pageSize * countHolder[0];
                    System.out.printf("NIO direct memory malloc'd size: %f MB (%d bytes)\n",
                            toM(totalMallocSize), totalMallocSize);
                }
            }
        } catch (AddressException e) {
            System.err.println("Error accessing address 0x"
                    + Long.toHexString(e.getAddress()));
            e.printStackTrace();
        }
    }

    public static long getStaticLongFieldValue(String className, String fieldName) {
        InstanceKlass klass = SystemDictionaryHelper.findInstanceKlass(className);
        LongField field = (LongField) klass.findField(fieldName, "J");
        return field.getValue(klass); // on JDK7 use: field.getValue(klass.getJavaMirror());
    }

    public static int getStaticIntFieldValue(String className, String fieldName) {
        InstanceKlass klass = SystemDictionaryHelper.findInstanceKlass(className);
        IntField field = (IntField) klass.findField(fieldName, "I");
        return field.getValue(klass); // on JDK7 use: field.getValue(klass.getJavaMirror());
    }

    public static double toM(long value) {
        return value / (1024 * 1024.0);
    }

    @Override
    public String getName() {
        return "directMemorySize";
    }

    @Override
    protected void printFlagsUsage() {
        System.out.println("    -e\tto print the actual size malloc'd");
        System.out.println("    -v\tto print verbose info of every live DirectByteBuffer allocated from Java");
        super.printFlagsUsage();
    }

    @Override
    public void start() {
        super.start();
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        boolean exactMallocMode = false;
        boolean verbose = false;

        // argument processing logic copied from sun.jvm.hotspot.tools.JStack
        int used = 0;
        for (String arg : args) {
            if ("-e".equals(arg)) {
                exactMallocMode = true;
                used++;
            } else if ("-v".equals(arg)) {
                verbose = true;
                used++;
            }
        }

        if (used != 0) {
            args = Arrays.copyOfRange(args, used, args.length);
        }

        DirectMemorySize tool = new DirectMemorySize(exactMallocMode, verbose);
        Class<DirectMemorySize> clazz = DirectMemorySize.class;
        Method method = clazz.getSuperclass().getDeclaredMethod("start");
        method.setAccessible(true);
        method.invoke(tool,args);
        tool.stop();
    }
}
